/******************************************************************************
 *  _____     _____
 *  \  __\    \  __\
 *   \ \_\\    \ \_\\
 *    \  __\SS  \  __\roject
 *     \_\       \_\
 *
 * Mateusz Maciąg, Rafał Urbański
 * Komputerowe Systemy Sterowania
 *****************************************************************************/

#ifndef CONFIGWRITER_H
#define CONFIGWRITER_H

#include <iosfwd>
#include <string>
#include <stack>
#include <map>
#include <stdexcept>

#include <boost/smart_ptr.hpp>

// Przestrzeń nazw
namespace RafMat
{
namespace SimFile
{

// Deklaracje zapowiadające
class ConfigWriter;

/** Alias na zręczny wskaźnik na ConfigWriter. */
typedef boost::shared_ptr<ConfigWriter> ConfigWriterSPtr;

/**
    Klasa wyjątków rzucanych przez ConfigWriter.

    Klasa jest typu std::logic_error, więc bloki catch mogą przechwytywać
    wyjątki tego typu.

    Klasa oferuje kilka metod statycznych generujących obiekty błędów z
    treścią odpowiadającą konkretnym problemom.
*/
class conf_writer_error : public std::logic_error
{
public:
    /**
        Konstruktor przypisujący dowolną wiadomość.
        \param [in] __arg Wiadomość przypisana bieżącej instancji.
    */
    explicit conf_writer_error(const std::string& __arg);

    /** */
    static conf_writer_error badBlock(const std::string& block);
};

class ConfigWriter
{
public:
    static const char COMMENT_CHAR = ';';
    static const char LBR_CHAR = '{';
    static const char RBR_CHAR = '}';

    /** Typy bloków konfiguracyjnych */
    enum BlockType
    {
        BlockSimulation,    ///< Blok ustawień globalnych symulatora.
        BlockModel,         ///< Blok ustawień modelu.
        BlockParams,        ///< Blok ustawień zestawu parametrów modelu.
        BlockInput,         ///< Blok ustawień wejść układu regulacji.
        BlockController,    ///< Blok ustawień regulatora.
        BlockNonlinearity   ///< Blok ustawień nieliniowości.
    };

    /**
        Konstruktor z podawanym strumieniem docelowym.
    */
    explicit ConfigWriter(std::ostream& outStream);

    /**
        Destruktor klasy.
        Finalizuje konfigurację. M.in. domyka niezamknięte bloki.
    */
    ~ConfigWriter();

    /**
        Dodaje do strumienia blok konfiguracyjny.
        Składnia bloku jest następująca:

        \code
        [BlockType nazwa]
        \endcode

        \param [in] blockType Typ bloku (zobacz BlockType).
        \param [in] blockName Nazwa bloku.
    */
    void pushBlock(BlockType blockType, std::string blockName) throw(conf_writer_error);

    /**
        Zamyka aktualny blok.

        \code
        [BlockType name]    ; Początek bloku
            ...
        [/BlockType]        ; Koniec bloku
        \endcode
    */
    void closeBlock() throw (conf_writer_error);

    /**
        Dodaje nowy parametr do konfiguracji.

        Przykład:
        \code
        [Model Silnik]
        [Params Poczatek]
        apoly = [ 3 5 6 1 ]     ; apoly -> paramName, [ 3 5 6 1 ] -> paramValue.
        \endcode

        \param [in] paramName Nazwa parametru. Nazwa musi spełniać takie wymogi
        jak identyfikatory w języku C/C++.
        \param [in] paramValue Wartość parametru. Może zawierać dowolne znaki.
        Sprawdzaniem składni zajmują się klasy, które przejmują te parametry.
        Znak średnika ';' jest zastrzeżony.
    */
    template <typename Tp>
    void pushParam(std::string paramName, Tp paramValue);

    /**
        Dodaje komentarz w bierzącej linii.
        Komentarz poprzedzony jest średnikiem.

        Przykład:
        \code
        [Simulation Motor driver] ; Simple comment
        \endcode
    */
    void pushLineComment(std::string comment);

    /**
        Dodaje długi komentarz od nowej linijki
        automatycznie przenosząc wyrazy.

        \param [in] comment Treść komentarza.
        \param [in] maxWidth Maksymalna długość komentarza w jednej linii (w znakach).
    */
    void pushMultilineComment(std::string comment, int maxWidth = 80);

    /**
        Dodaje określoną liczbę pustych linii.
        \param [in] howMuch Liczba pustych linijek.
    */
    void pushEmptyLine(unsigned int howMuch = 1);

    /**
        Ustawia rozmiar wcięcia.

        \param [in] indent Rozmiar wcięcia w liczbie spacji.
        Domyślnie rozmiar wynosi 4 (standardowa wartość).
    */
    void setIndentSize(unsigned int indent = 4);

    /**
        Ustawia wyrównanie linii komentarza.
        Wartość ta oznacza liczbę znaków, której wielokrotność
        oznacza pozycję średnika ';'. Pozwala to na uzyskanie
        miłego efektu równego ułożenia komentarzy:

        \code
        param = value       ; Jakis komentarz
        x = y               ; Jakis komentarz
        \endcode

        Zamiast:

        \code
        param = value       ; Jakis komentarz
        x = y       ; Jakis komentarz
        \endcode

        \param [in] adjSize Wielkość wyrównania.
    */
    void setLineCommentAlignment(unsigned int adjSize) throw();

    void enableComments(bool f = true) { m_enableComments = f; }
private:

    /** Zwiększa wcięcie. */
    void incrementIndent() throw();

    /** Zmniejsza wcięcie. */
    void decrementIndent() throw();

    /** Wrzuca wcięcie do strumienia. */
    void pushIndent();

    /** Inicjalizacja mapy bloków. */
    void initializeBlockMap();

    /** Finalizuje wypisywanie konfiguracji.
        Metoda sprawia, że bloki zostają domknięte.
    */
    void closeUnclosedBlocks();

    /** Odświeża numer kolumny. */
    void updateColumns();

    std::ostream& m_outputStream;

    /** Aktualny stos zagłębienia bloków. */
    std::stack<BlockType> m_blockStack;

    unsigned int m_indentSize;      ///< Rozmiar pojedynczego wcięcia.
    unsigned int m_indentLevel;     ///< Liczba wcięć.
    unsigned int m_commentAlignment; ///< Wyrównanie komentarza.

    typedef std::map<BlockType, std::string> BlockMap;

    /** Łańcuchy znakowe odpowiadające typom bloków */
    BlockMap m_blockNames;

    /** Licznik wierszy */
    int m_rows;

    /** Licznik kolumn */
    int m_columns;

    /** Pozycja początku bierzącej linii w strumieniu */
    int m_streamPos;

    bool m_alignmentSupport;

    bool m_enableComments;
};

template <typename Tp>
void ConfigWriter::pushParam(std::string paramName, Tp paramValue)
{
    pushEmptyLine();
    pushIndent();

    m_outputStream << paramName << " = " << paramValue;
    updateColumns();
}

}
}

#endif // CONFIGWRITER_H
